
#include <Inklude/Internal/ikCUdpBoost.h>
#include <Inklude/ikCUdpClient.h>
#include <Inklude/ikCThread.h>
#include <boost/bind.hpp>


using namespace Inklude::Internal;

static const unsigned int MAX_UDP_PACKET_SIZE = 60000;


ikCUdpBoost::ikCUdpBoost()
{
}

ikCUdpBoost::~ikCUdpBoost()
{
}

void ikCUdpBoost::Initialize(unsigned int nRecvPort)
{
    m_pIOService.reset(new boost::asio::io_service());
    m_pSocket.reset(new udp::socket(*m_pIOService, udp::endpoint(udp::v4(), nRecvPort)));
    isRun = true;
}

void ikCUdpBoost::Run()
{
    if (isRun)
    {
        isRun = false;
        std::shared_ptr<std::function<void()> > ioThread(new std::function<void()>( [=]()
        {
            m_pIOService->run();
        }
        ));
        Inklude::ikCThread::RunThread(ioThread);
    }
}

void ikCUdpBoost::SetDestinationAddress(const char* IpAddress, unsigned int nSentPort)
{
    Inklude::ikCString sendPort;
    sendPort = (int)nSentPort;
    udp::resolver _resolver(*(m_pIOService.get()));
    udp::resolver::query _query(udp::v4(),  IpAddress, sendPort.c_str());
    udp::resolver::iterator iter = _resolver.resolve(_query);

    m_pRemoteEndPoint.reset(new udp::endpoint((iter->endpoint()).address(), (iter->endpoint()).port()));
}

size_t ikCUdpBoost::SyncSend(void* pBuffer, size_t nDataSize)
{
    size_t retVal = 0;
    try
    {
        if (MAX_UDP_PACKET_SIZE < nDataSize)
        {
            size_t sentSize = 0;
            size_t remaindSize = nDataSize;
            while (remaindSize > MAX_UDP_PACKET_SIZE)
            {
                size_t realSentSize = (m_pSocket->send_to(boost::asio::buffer(((char*)pBuffer) + sentSize, MAX_UDP_PACKET_SIZE), *m_pRemoteEndPoint));
                if (realSentSize != MAX_UDP_PACKET_SIZE)
                {
                    throw std::exception("send error");
                }
                sentSize += MAX_UDP_PACKET_SIZE;
                remaindSize -= MAX_UDP_PACKET_SIZE;
            }
            size_t realSentSize = m_pSocket->send_to(boost::asio::buffer(((char*)pBuffer) + sentSize, remaindSize), *m_pRemoteEndPoint);
            if (realSentSize != remaindSize)
            {
                throw std::exception("send error");
            }
            retVal = nDataSize;
        }
        else
        {
            retVal = m_pSocket->send_to(boost::asio::buffer(pBuffer, nDataSize), *m_pRemoteEndPoint);
        }
    }
    catch (std::exception& e)
    {
        std::cerr << e.what() << std::endl;
        retVal = 0;
    }
    return retVal;
}

size_t ikCUdpBoost::AsyncSend(void* pBuffer, size_t nDataSize, AsyncHandler endHandler)
{
    size_t retVal = 0;

    if (m_sendHandler.get() != endHandler.get())
    {
        m_sendHandler = endHandler;
    }
    if (nullptr != m_pRemoteEndPoint)
    {
        try
        {
            //m_pIOService->run();
            //m_pIOService->stop();
            m_pSocket->async_send_to(boost::asio::buffer(pBuffer, nDataSize), 
                                     *m_pRemoteEndPoint,
                                     boost::bind(&ikCUdpBoost::handle_send, 
                                                 this, 
                                                 boost::asio::placeholders::error, 
                                                 boost::asio::placeholders::bytes_transferred)
                                     );
            //m_pIOService->run();
        }
        catch (std::exception& e)
        {
            std::cerr << e.what() << std::endl;
        }
    }
    return retVal;
}

size_t ikCUdpBoost::SyncReceive(void* pBuffer, size_t nDataSize)
{
    boost::system::error_code error;
    size_t retVal = 0;
    try
    {
        if (MAX_UDP_PACKET_SIZE < nDataSize)
        {
            size_t recvSize = 0;
            size_t remaindSize = nDataSize;
            while (remaindSize > MAX_UDP_PACKET_SIZE)
            {
                size_t realSentSize = m_pSocket->receive(boost::asio::buffer(((char*)pBuffer) + recvSize, MAX_UDP_PACKET_SIZE));
                if (realSentSize != MAX_UDP_PACKET_SIZE)
                {
                    throw std::exception("receive error");
                }
                recvSize += MAX_UDP_PACKET_SIZE;
                remaindSize -= MAX_UDP_PACKET_SIZE;
            }
            size_t realSentSize = m_pSocket->receive(boost::asio::buffer(((char*)pBuffer) + recvSize, MAX_UDP_PACKET_SIZE));
            if (realSentSize != remaindSize)
            {
                throw std::exception("receive error");
            }
            retVal = nDataSize;
        }
        else
        {
            retVal = m_pSocket->receive(boost::asio::buffer(((char*)pBuffer), nDataSize));
        }
    }
    catch (std::exception& e)
    {
        std::cerr << e.what() << std::endl;
        retVal = 0;
    }
    return retVal;
}

bool isRun = false;

size_t ikCUdpBoost::AsyncReceive(void* pBuffer, size_t nDataSize, AsyncHandler endHandler)
{
    size_t retVal = 0;
    if (m_recvHandler.get() != endHandler.get())
    {
        m_recvHandler = endHandler;
    }
    try
    {
        m_pSocket->async_receive(boost::asio::buffer(pBuffer, nDataSize),
                                 boost::bind(&ikCUdpBoost::handle_receive, 
                                             this, 
                                             boost::asio::placeholders::error, 
                                             boost::asio::placeholders::bytes_transferred) );
    }
    catch (std::exception& e)
    {
        std::cerr << e.what() << std::endl;
    }
    return retVal;
}

void ikCUdpBoost::handle_receive(const boost::system::error_code& error, std::size_t bytes_transferred)
{
    if (!error || error == boost::asio::error::message_size)
    {
        (*m_recvHandler)(bytes_transferred);
    }
}

void ikCUdpBoost::handle_send(const boost::system::error_code& error, std::size_t bytes_transferred)
{
    if (!error || error == boost::asio::error::message_size)
    {
        (*m_sendHandler)(bytes_transferred);
    }
}


